#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#===============================================================================
# Copyright 2011 zod.yslin
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# Author: zod.yslin
# Email: 
# File Name: syntax_new.py
# Description: 
#   This section gives a brief overview of every syntactic change in Python 3.0.
#   1. PEP 3107: Function argument and return value annotations. 
#   2. PEP 3102: Keyword-Only Arguments, for foo(*varargs)
#   3. PEP 3104: Access to Names in Outer Scopes
#   4. PEP 3132: Extended Iterable Unpacking
#   5. PEP 274 : Dict Comprehensions
#   6. Set literals
#   7. New octal literals
#   8. New binary literals
#   9. Bytes literals
# Edit History: 
#   2011-09-08    File created.
#===============================================================================

#===============================================================================
#   1. PEP 3107: Function argument and return value annotations. 
#                This PEP introduces a syntax for adding arbitrary metadata annotations to Python functions
#                lambda's syntax does not support annotations.
#===============================================================================
def compile(source: "something compilable",
            filename: "where the compilable thing comes from",
            mode: "is this a single statement or a suite?"):
    """
    use string-based annotations to provide improved help messages
    """
    print(source, filename, mode)

compile("a", "b", "c")


def gcd(a: int, b: int) -> int:
    """
    provide typechecking for Python functions and methods
    """
    while a:
        a, b = b%a, a
        return b
print(gcd(10, 5))

def foo(x:'int' = 0) -> 'str':
    '''An example function that fails type checking.'''
    return x

print(foo(9527))   # 9527
print(foo('9527'))  # 9527

#print(foo.func_annotations)  # 2.7
print(foo.__annotations__) # 3.0, {'x': 'int', 'return': 'str'}

def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
    pass
print(foo.__annotations__) # 3.0, {'a': 'x', 'c': <class 'list'>, 'b': 11, 'return': 9}

#===============================================================================
#   2. PEP 3102: Keyword-Only Arguments
#                Named parameters occurring after *args in the parameter list 
#                must be specified using keyword syntax in the call. 
#                You can also use a bare * in the parameter list to indicate 
#                that you don’t accept a variable-length argument list, 
#                but you do have keyword-only arguments.
#===============================================================================
def sortwords(*wordlist, case_sensitive=False):
    """
    Allow regular arguments to appear after a varargs argument (*wordlist is a 
    varargs like c)
    """
    pass

sortwords('a')
sortwords(1, 2)
sortwords('a', 'b', 'c')

def compare1(a, b, key=None):
    """
    key will be taken positional arguments as its value.
    """
    print(a,b,key)
    pass

def compare2(a, b, *, key=None):
    """
    Allow the argument name to be omitted for a varargs argument,
    otherwise it cause error.
    The meaning of this is to
    allow for keyword-only arguments for functions that would not
    otherwise take a varargs argument
    """
    print(a,b,key)
    pass

def compare3(a, b, *ignore, key=None):
    """
    The 'ignore' argument will also suck up any
    erroneous positional arguments that may have been supplied by the
    caller.  Given that we'd prefer any unwanted arguments to raise an
    error.
    """
    if ignore:  # If ignore is not empty
        raise TypeError
    print(a,b,key)

compare1(1,2,3)
#compare2(1,2,3)  # TypeError: compare2() takes exactly 2 positional arguments (3 given)
compare2(1,2,key='I am a key')  
#compare3(1,2,3)  # TypeError
compare3(1,2,key='key2')  

#===============================================================================
#   3. PEP 3104: Access to Names in Outer Scopes
#                Using nonlocal x you can now assign directly to a variable 
#                in an outer (but non-global) scope. nonlocal is a new reserved word.
#===============================================================================
# global
def func(x):
    print('x is', x)
    x = 2
    print('Changed local x to', x)

x = 50
func(x)
print('x is still', x)

def func():
    global x

    print('x is', x)
    x = 2
    print('Changed local x to', x)

x = 50
func()
print('Value of x is', x)

# nonlocal
def outer():
    x = 1
    def inner():
        x = 2
        print("inner:", x)
    inner()
    print("outer:", x)

outer()

def outer():
    x = 1
    def inner():
        nonlocal x
        x = 2
        print("inner:", x)
    inner()
    print("outer:", x)

outer()
#===============================================================================
#   4. PEP 3132: Extended Iterable Unpacking
#                You can now write things like a, b, *rest = some_sequence. 
#                And even *rest, a = stuff. The rest object is always a 
#                (possibly empty) list; the right-hand side may be any iterable.
#===============================================================================
a, *b, c = range(5)
print(a)  # 0 
print(b)  # 1,2,3
print(c)  # 4

seq = [1,2,3,4,5]
# Many algorithms require splitting a sequence in a "first, rest" pair. Old style:
first, rest = seq[0], seq[1:]
print(first) # 1
print(rest)  # 2,3,4,5
# cleaner and probably more efficient style
first, *rest = seq
print(first) # 1
print(rest)  # 2,3,4,5

it = iter(seq)
first = it.__next__()
print(first) # 1
rest = list(it)
print(rest)  # 2,3,4,5


a, b, c = seq[0], seq[1:-1], seq[-1]
print(a)  # 1 
print(b)  # 2,3,4
print(c)  # 5
a, *b, c = seq
print(a)  # 1 
print(b)  # 2,3,4
print(c)  # 5
[a, *b, c] = seq
print(a)  # 1 
print(b)  # 2,3,4
print(c)  # 5

#*a = range(5) # error
*a, = range(5) # valid
print(a)


for a, *b in [(1, 2, 3), (4, 5, 6, 7)]:
    print(b)
    #[2, 3]
    #[5, 6, 7]

#===============================================================================
#   5. PEP 274 : Dict Comprehensions
#                Dict comprehensions are just like list comprehensions, except that
#                you group the expression using curly braces instead of square
#                braces.
#===============================================================================

# semantically equivalent
print(dict([(i, chr(65+i)) for i in range(4)]))
print({i : chr(65+i) for i in range(4)})

# semantically equivalent
someDict = {'1':2, '3':4}
print({k : v for k, v in someDict.items()} == someDict.copy())  # True
#print({k : v for k, v in someDict.iteritems()} == someDict.copy())  # version 2.2

list_of_email_addrs = ['barry@zope.com', 'barry@python.org', 'guido@python.org']
print({x.lower() : 1 for x in list_of_email_addrs})

def invert(d):
    return {v : k for k, v in d.items()}
    #return {v : k for k, v in d.iteritems()}  # version 2.2

d = {0 : 'A', 1 : 'B', 2 : 'C', 3 : 'D'}
print(invert(d)) # {'A' : 0, 'B' : 1, 'C' : 2, 'D' : 3}

print({(k, v): k+v for k in range(4) for v in range(4)})

#===============================================================================
#   6. Set literals
#===============================================================================
# {1, 2}. Note that {} is an empty dictionary; use set() for an empty set. 
print({1, 2}) 
print({})
print(set())
stuff = [1,2,3]

# Set comprehensions are also supported;
# e.g., {x for x in stuff} means the same thing as set(stuff) but is more flexible.
print({x for x in stuff})
print(set(stuff))
#===============================================================================
#   7. New octal literals
#===============================================================================
#e.g. 0o720 (already in 2.6). The old octal literals (0720) are gone.
print(0o10)
print(0x10)

#===============================================================================
#   8. New binary literals
#===============================================================================
#e.g. 0b1010 (already in 2.6), and there is a new corresponding builtin function, bin().
print(0b10)
print(bin(3))
print(0b10 == 2) # True
print(0b10 == bin(2)) # False
#===============================================================================
#   9. Bytes literals
#===============================================================================
#Bytes literals are introduced with a leading b or B, and there is a new 
#corresponding builtin function, bytes().
print(b'abc')
print(bytes([97,98,99])) 
print(bytes.fromhex('616263'))
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
